﻿using RpcCacheClient.Helper;
using RpcClient.Interface;
using RpcHelper;
using RpcModel;
using RpcModular.Model;
using RpcModularModel.Accredit;
using RpcModularModel.Accredit.Model;
using RpcSync.Service.Model;
using RpcSyncService.Interface;

namespace RpcSyncService.Accredit
{
    public enum AccreditType
    {
        redis = 0,
        db = 1
    }
    [RpcClient.Attr.ClassLifetimeAttr(RpcClient.Attr.ClassLifetimeType.单例)]
    internal class AccreditServer : IAccreditServer
    {
        private readonly IAccreditCollect _Server;
        private readonly IAccreditKeyCollect _AccreditKey;
        private readonly DelayDataSave<Guid> _RefreshQueue;
        private readonly IDataQueueHelper<SyncAccredit> _AccreditQueue = null;
        private readonly IDataQueueHelper<Guid> _ClearQueue;

        public AccreditServer(ISysConfig config, IAccreditKeyCollect accreditKey,IUnityCollect unity)
        {
            this._AccreditKey = accreditKey;
            AccreditType accreditType = config.GetConfigVal<AccreditType>("sync:accredit:AccreditType", AccreditType.redis);
            this._Server = unity.Resolve<IAccreditCollect>(accreditType.ToString());
            this._RefreshQueue = new DelayDataSave<Guid>(this._Refresh, _FilterData, 60, 100);
            this._ClearQueue = new RedisDataQueue<Guid>("清理作废授权", this._Clear, 60, 100);
            this._AccreditQueue = new RedisDataQueue<SyncAccredit>("同步授权状态", this._SyncAccredit, 100, 100);
        }

        public ApplyAccreditRes ApplyAccredit(ApplyAccredit apply, MsgSource source)
        {
            apply.AccreditRole = apply.AccreditRole.Add(source.SystemType);
            AccreditToken token = this._CreateToken(apply, source);
            this._Server.Accredit(token);
            SyncAccredit obj = new SyncAccredit
            {
                AccreditId = token.AccreditId,
                ApplyKey = token.CheckKey,
                Expire = token.Expire
            };
            this._AccreditQueue.AddQueue(obj);
            return new ApplyAccreditRes
            {
                StateVer = token.StateVer,
                Accredit = new AccreditRes
                {
                    CheckKey = token.CheckKey,
                    AccreditId = token.AccreditId,
                    SysGroup = token.SysGroup,
                    RpcMerId = token.RpcMerId,
                    ApplyId = token.ApplyId,
                    Expire = token.Expire
                }
            }; ;
        }
        public void Cancel(Guid[] accreditId)
        {
            this._ClearQueue.AddQueue(accreditId);
        }
        public void CancelAccredit(Guid accreditId, string checkKey)
        {
            if (this._AccreditKey.TryRemove(checkKey, accreditId))
            {
                this._CancelAccredit(accreditId);
            }
        }

        public void CheckAccredit(CheckAccredit obj)
        {
            if (!this._AccreditKey.Check(obj.CheckKey, obj.AccreditId))
            {
                throw new ErrorException("accredit.Invalid");
            }
            else if (obj.IsRefresh)
            {
                this._RefreshQueue.AddData(obj.AccreditId);
            }
        }

        public AccreditDatum GetAccredit(Guid accreditId, MsgSource source)
        {
            if (!this._Server.Get(accreditId, out IAccreditToken token))
            {
                throw new ErrorException("accredit.Invalid");
            }
            else if (!token.CheckRole(source))
            {
                throw new ErrorException("accredit.no.prower");
            }
            else
            {
                return token.Get();
            }
        }

        public void KickOutAccredit(string checkKey)
        {
            Guid? accreditId = this._AccreditKey.KickOut(checkKey);
            if (accreditId.HasValue)
            {
                this._CancelAccredit(accreditId.Value);
            }
        }

        public SetUserStateRes SetUserState(SetUserState obj)
        {
            if (!this._Server.Get(obj.AccreditId, out IAccreditToken accredit))
            {
                throw new ErrorException("accredit.Invalid");
            }
            else
            {
                bool isSuccess = accredit.SetState(obj.UserState);
                AccreditToken token = accredit.Token;
                return new SetUserStateRes
                {
                    IsSuccess = isSuccess,
                    StateVer = token.StateVer,
                    UserState = isSuccess ? null : token.State
                };
            }
        }

        public ApplyAccreditRes ToUpdate(SetAccredit obj)
        {
            if (!this._Server.Get(obj.AccreditId, out IAccreditToken accredit))
            {
                throw new ErrorException("accredit.Invalid");
            }
            else
            {
                accredit.Set(obj);
                AccreditToken token = accredit.Token;
                return new ApplyAccreditRes
                {
                    StateVer = token.StateVer,
                    Accredit = new AccreditRes
                    {
                        CheckKey = token.CheckKey,
                        AccreditId = token.AccreditId,
                        SysGroup = token.SysGroup,
                        RpcMerId = token.RpcMerId,
                        Expire = token.Expire,
                        ApplyId = token.ApplyId
                    }
                };
            }
        }

        private AccreditToken _CreateToken(ApplyAccredit apply, MsgSource source)
        {
            if (!apply.ParentId.HasValue)
            {
                return new AccreditToken
                {
                    AccreditId = apply.AccreditId,
                    RoleType = apply.RoleType,
                    Expire = apply.Expire.HasValue ? DateTime.Now.AddSeconds(apply.Expire.Value) : null,
                    SysGroup = source.SysGroup,
                    ApplyId = apply.ApplyId,
                    RpcMerId = source.RpcMerId,
                    CheckKey = string.Concat(apply.RoleType, "_", apply.ApplyId).GetMd5(),
                    AccreditRole = apply.AccreditRole,
                    State = apply.State,
                    SystemType = source.SystemType,
                    StateVer = 1
                };
            }
            else if (!this._Server.Get(apply.ParentId.Value, out IAccreditToken parent))
            {
                throw new ErrorException("accredit.parent.Invalid");
            }
            else
            {
                return _CreateToken(apply, parent.Token, source);
            }
        }
        private static string _MergeState(ApplyAccredit apply, AccreditToken parent)
        {
            if (parent.State.IsNull())
            {
                return apply.State;
            }
            UserState one = parent.State.Json<UserState>();
            UserState two = apply.State.Json<UserState>();
            return two.Merge(one);
        }
        private static AccreditToken _CreateToken(ApplyAccredit apply, AccreditToken parent, MsgSource source)
        {
            if (apply.IsInherit)
            {
                apply.AccreditRole = apply.AccreditRole.IsNull() ? parent.AccreditRole : parent.AccreditRole.Add(apply.AccreditRole).Distinct();
                apply.State = apply.State.IsNull() ? parent.State : _MergeState(apply, parent);
            }
            else if (apply.AccreditRole == null)
            {
                apply.AccreditRole = Array.Empty<string>();
            }
            DateTime? time = apply.Expire.HasValue ? DateTime.Now.AddSeconds(apply.Expire.Value) : null;
            if (time.HasValue && parent.Expire.HasValue && time.Value > parent.Expire.Value)
            {
                time = parent.Expire;
            }
            string checkKey = apply.ApplyId.IsNotNull() ? string.Concat(apply.RoleType, "_", apply.ApplyId).GetMd5() : apply.AccreditId.ToString("N");
            return new AccreditToken
            {
                AccreditId = apply.AccreditId,
                RoleType = parent.RoleType,
                Expire = time,
                SysGroup = source.SysGroup,
                ApplyId = apply.ApplyId ?? apply.AccreditId.ToString("N"),
                RpcMerId = source.RpcMerId,
                CheckKey = checkKey,
                PAccreditId = parent.AccreditId,
                AccreditRole = apply.AccreditRole,
                State = apply.State,
                SystemType = source.SystemType,
                StateVer = 1
            };
        }

        /// <summary>
        /// 去重
        /// </summary>
        /// <param name="datas"></param>
        private static void _FilterData(ref Guid[] datas)
        {
            datas = datas.Distinct().ToArray();
        }
        /// <summary>
        /// 刷新授权状态
        /// </summary>
        /// <param name="accredits"></param>
        /// <exception cref="ErrorException"></exception>
        private void _Refresh(ref Guid[] accredits)
        {
            accredits.ForEach(a =>
            {
                if (this._Server.Get(a, out IAccreditToken token))
                {
                    TimeSpan? time = token.Refresh();
                    if (time.HasValue)
                    {
                        this._AccreditKey.Renewal(token, time.Value);
                    }
                }
            });
        }
        /// <summary>
        /// 同步授权状态
        /// </summary>
        /// <param name="obj"></param>
        private void _SyncAccredit(SyncAccredit obj)
        {
            TimeSpan? time = AccreditHelper.GetAccreditTime(obj.Expire);
            if (!time.HasValue)
            {
                return;
            }
            Guid old = this._AccreditKey.Set(obj.ApplyKey, obj.AccreditId, time.Value);
            if (old != Guid.Empty && old != obj.AccreditId)
            {
                this._CancelAccredit(old);
            }
        }
        /// <summary>
        /// 清理授权
        /// </summary>
        /// <param name="accreditId"></param>
        private void _Clear(Guid accreditId)
        {
            if (this._Server.Get(accreditId, out IAccreditToken token) && token.Cancel())
            {
                this._AccreditKey.TryRemove(token.CheckKey, token.AccreditId);
            }
        }

        private void _CancelAccredit(Guid accreditId)
        {
            if (this._Server.Get(accreditId, out IAccreditToken token))
            {
                token.Cancel();
            }
        }

    }
}
